Skip to content

feat(libkernel): implement getcontext, setcontext and makecontext - #1841

Closed
DariusHQ wants to merge 1 commit into
boykopovar:mainfrom
DariusHQ:feat/libkernel-ucontext
Closed

DariusHQ wants to merge 1 commit into
boykopovar:mainfrom
DariusHQ:feat/libkernel-ucontext

Conversation

@DariusHQ

@DariusHQ DariusHQ commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

What

getcontext (dar03AOn+nM) and setcontext (2VRU7xiqLO8) in libkernel and makecontext (GipcbdDM5cI) in libSceLibcInternal are missing, so the 15 titles that #1578 lists for them stop at start-up. Part of #1578; no open PR implements them.

In PPSA99039 (EVO Player 0.10.0) every call site is in the statically linked OpenSSL's ASYNC code (crypto/async/arch/async_posix.c): ASYNC_is_capable, async_fibre_makecontext, and async_fibre_swapcontext, which enters a new fibre with setcontext and switches afterwards with _setjmp/_longjmp.

getcontext is written in assembly, so it saves every register as it was at the call. It stores the context as FreeBSD's sys_getcontext does: rax and rdx as 0, the carry flag clear, rip and rsp as after the return, the FXSAVE image, mc_len, and the signal mask (through sigprocmask). Like UC_COPY_SIZE, it writes only the 0x4c0 bytes before uc_link, so uc_link, uc_stack and uc_flags keep what the caller stored.

setcontext fails with EINVAL on a null context, a wrong mc_len or unknown mc_flags bits, as set_mcontext does. It installs the context's signal mask, masks MXCSR with the host's MXCSR_MASK as fpusetregs does (FXRSTOR faults on reserved bits), and returns through iretq: every general register, rip, rsp and the PSL_USERCHANGE flags come from the context, and nothing is written below the target stack pointer, which matters for a context taken in a signal handler.

makecontext follows FreeBSD 11's lib/libc/amd64/gen/makecontext.c: at most six arguments and a stack of at least MINSIGSTKSZ (2048) bytes, otherwise mc_len becomes 0. Its wrapper calls the function, then setcontext(uc_link), or exit(0) when there is no link. It lives in the libSceLibcInternal module, which links libkernel.

The three use the layout of the exception handler's context (mcontext at 0x40, 0x500 bytes), which System/include/GuestContext.hpp now declares for them. shadPS4 passes the same layout to sigaction handlers, and Unity's PS5Util reads mc_rsp at 0xf8. Homebrew built against FreeBSD's headers expects FreeBSD's 0x370-byte layout instead: PPSA99039's ASYNC_is_capable passes a 0x370-byte stack buffer. This is recorded in TechnicalDebt.

Exception.cpp keeps its own copy of the structures for now: #1431, #1562 and #1802 all change them, and moving them here would conflict with all three. Once those land, Exception.cpp can include the header in a follow-up.

Windows stack bounds. Windows checks guard-page growth against StackBase and DeallocationStack in the TEB, and exception dispatch and unwinding against StackLimit and StackBase, so the TEB must describe the stack the thread runs on, as libSceFiber keeps it for fiber stacks. A new libc helper, GuestStacks, does this for setcontext and longjmp. makecontext registers its stack and pins it writable, as fiber stacks are pinned. A switch to a registered stack takes its bounds; any other switch takes the committed region around the target rsp from VirtualQuery, which for a thread stack is its current StackLimit to StackBase. The bounds change after the last write to the old stack. longjmp needs this because OpenSSL leaves a fibre with _longjmp: without it the thread would run on its own stack with the fibre's bounds, and its next guard-page hit would raise STATUS_GUARD_PAGE_VIOLATION. Linux gets the three functions and no other change.

TechnicalDebt records the unverified layout and the FreeBSD-header mismatch, the FreeBSD source of makecontext, what the FXSAVE-only state and setcontext leave out, the Windows pin, and the bounds fallback.

A test merge of this branch with every other open pull request on 364a56b6 conflicts only with #1128, which reformats the whole tree, including the setjmp/longjmp assembly. The overlap report's __error_nid_postfix entries are declarations of the existing libc export, as in nine files on main; this PR does not export it.

Tested

Windows 11 (26200), MinGW-w64 GCC 15.2.0 (WinLibs r7), Release, full build with BUILD_TESTING=ON on 364a56b6: ctest 483/483 passed. guest_user_context and guest_user_context_exit passed 200 runs in a row (--repeat until-fail:200).

guest_user_context covers:

  • errors: null contexts, a wrong mc_len, unknown mc_flags; makecontext with seven arguments, a 2047-byte stack, no stack, and an invalid context;
  • capture: an assembly caller loads known values into every register and sets the carry flag; the test checks each saved register, rax/rdx 0, carry clear, rip, rsp, MXCSR, the x87 control word, the signal mask, the zeroed fields, and that nothing from uc_link on changes;
  • restore: a context with patterns in every general register, XMM0-15, MXCSR (with a reserved bit set), the x87 control word and the flags lands in an assembly stub that records them;
  • makecontext with six 64-bit arguments, an aligned entry stack and a return through uc_link;
  • OpenSSL's pattern: setcontext into a job, then four rounds of setjmp/longjmp both ways;
  • the signal mask restored by setcontext;
  • on Windows: the TEB bounds after every switch, a C++ exception thrown and caught on the context stacks, and about 512 KiB of new host stack after returning.

guest_user_context_exit checks that a context without uc_link exits with 0.

Mutation check, all caught: setcontext or longjmp keeping the old bounds, makecontext not registering its stack, getcontext keeping the carry flag, writing past uc_link or saving rcx as rbx, setcontext keeping reserved MXCSR bits, ignoring the context's flags, not loading r11, keeping the current signal mask or accepting a wrong mc_len, makecontext shifting its arguments or misaligning the entry stack, and a context without uc_link aborting instead of exiting.

tools/import_audit.py on PPSA99039 (EVO Player 0.10.0): 24 absent imports on main, 21 with this branch; the three are resolved, and the built prx export the NIDs above. No title run: PPSA99039 still has other absent imports.

Not run on Linux (no Linux host here). The Linux path is the same assembly without the TEB call; CI runs it.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
#1772 __error_nid_postfix
#1860 __error_nid_postfix
#2024 core/libs/prx/libSceLibcInternal/CMakeLists.txt L1 __error_nid_postfix
#2051 __error_nid_postfix

@DariusHQ
DariusHQ force-pushed the feat/libkernel-ucontext branch from 10c72da to 0682b32 Compare October 8, 2026 21:23
Fifteen titles listed in boykopovar#1578 stop at start-up on getcontext and
setcontext (libkernel) and makecontext (libSceLibcInternal). In
PPSA99039 the three are imported by the statically linked OpenSSL's
ASYNC code.

The three use the exception handler's ucontext layout, which
GuestContext.hpp declares for them. getcontext saves every register at
its call in assembly and stores the context as FreeBSD's sys_getcontext
does: rax and rdx as 0, the carry flag clear, the FXSAVE image and the
signal mask, and nothing from uc_link on. setcontext checks mc_len and
mc_flags as set_mcontext does, installs the signal mask, masks MXCSR
with the host's MXCSR_MASK and returns through iretq, so every general
register and the flags are restored and nothing is written below the
target stack pointer. makecontext follows FreeBSD 11's amd64 libc.

Windows checks guard-page growth and exception unwinding against the
stack bounds in the TEB. setcontext and longjmp now set them to the
stack they switch to, which makecontext registers and pins writable as
libSceFiber does for fiber stacks. OpenSSL enters a context with
setcontext and then switches with _setjmp and _longjmp.
@oneandonlydean

Copy link
Copy Markdown
Collaborator

Closing: no title stops at getcontext, setcontext or makecontext on main; EVO-PLAYER stops at other imports first, and its OpenSSL passes FreeBSD's 0x370-byte ucontext while this uses the 0x500-byte layout. Reopen when these are a title's next stop on main, with the log line.

ycbuzi pushed a commit to ycbuzi/AnyPS5 that referenced this pull request Oct 10, 2026
…ontext and makecontext

Harvested from upstream (closed unmerged); library completion 2851/3241 -> 2853/3243.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011krDVCnnMQPaEFEqCDnKmA
@babyhuey

Copy link
Copy Markdown
Contributor

@DariusHQ setcontext is now the next stop for three catalog titles on main 52ce9b8 (Linux, catalog builds with sce_module/libc.prx unwrapped as #2605 requires), each with app.elf: symbol lookup error: app.elf: undefined symbol: 2VRU7xiqLO8:

  • PSXS5 (PPSA97510)
  • SwanStationPS5 (PPSA98510)
  • Nuvio PS5 (PPSA99997)

Three more reach it right after other open fixes: EVO Player after pthread_getaffinity_np (#1922), and PS5MC and Emby5 after getprogname (#1869).

The closing note's layout point still applies: EVO Player's OpenSSL passes FreeBSD's 0x370-byte ucontext, and this branch uses 0x500. Intake needs 10 merged PRs to open a new one. Would you like to update the layout, or would you rather I carry it with your authorship and make that change on top?

@LeandroLP23

Copy link
Copy Markdown
Contributor

Windows data for the count above (Windows 11, WinLibs GCC 15.2.0 r7, Release, titles relinked with their own libc.prx unwrapped):

Once this is reopened I can run both titles on Windows with it.

@orishamir

Copy link
Copy Markdown
Contributor

@oneandonlydean two more catalog titles import all three, from tools/import_audit.py on main ccf9ff9 with their eboot.bin and libc.prx unwrapped (details in the linked comments): ProsperoStore (PPSA99000, #2908) reaches getcontext/setcontext/makecontext after _umtx_op (#1482) and getprogname (#3347); Cloud Play (PPSA99600, #3249) after localeconv, getprogname and _umtx_op. With @babyhuey's three titles that stop at setcontext on main today and @LeandroLP23's Windows run of EVO Player, that is nine catalog titles on this thread that need the three, and the condition in the closing note is met. Nothing else open implements them; the ucontext size question (0x370 vs 0x500) is the one point left for whoever carries it.

AI-assisted: yes (Claude Code Fable 5.1 xhigh)

@orishamir

Copy link
Copy Markdown
Contributor

The log line for the closing note's condition, now that #1482 is merged: ProsperoStore (PPSA99000, catalog v1.000.050) stops at start-up on setcontext on main 09bd4aa + 3879e7e (the #1482 merge), Windows 11, WinLibs GCC 15.2.0 r7, relinker --windows --windows-diagnostics --registry:

FAIL: unresolved ELF import 2VRU7xiqLO8 from libkernel.prx

Windows binds setcontext before getprogname here, as @LeandroLP23 saw for EVO Player; on Linux the loader reports whichever of the two it reaches first.

AI-assisted: yes (Claude Code Fable 5.1 xhigh)

@DariusHQ

Copy link
Copy Markdown
Contributor Author

Opened #3421 with FreeBSD's 0x370-byte layout. Thanks for the logs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants